﻿using System;
using System.Collections.Generic;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using SmlEngine.Timers;
using SmlEngine.UI.Input;
using SmlEngine.UI.Managers;
using SmlEngine.UI.Screens;

namespace SmlEngine.UI
{
    public class MenuScene : IScene
    {
        #region Fields

        //Constants
        private const float selectedScale = 1.25f;
        private static readonly Color selectedColor = Color.Red;

        //Entries
        private readonly List<MenuEntry> menuEntries;
        private bool isTransitioning;
        private int selectedIndex = 0;

        //Transitioning
        private float alpha;
        private Rectangle originalArea;

        private int previousCount = 0;
        private bool checkCount = true;

        private bool noInput = true;

        #endregion

        #region Properties

        public int SelectedIndex
        {
            get { return selectedIndex; }
            set
            {
                MoveToItem(selectedIndex, value);
                selectedIndex = value;
            }
        }

        public IList<MenuEntry> MenuEntries
        {
            get { return menuEntries; }
        }

        public float FadeTime { get; set; }
        public float TransitionTime { get; set; }

        public Action<MenuScene> Cancelled { get; set; }

        public SpriteFont Font { get; set; }
        public Color SelectedColor { get; set; }

        public int Height { get; set; }

        public Screen Screen { get; set; }

        #endregion

        #region Methods

        public MenuScene(Screen screen)
        {
            Screen = screen;
            screen.Scenes.Add(this);

            TransitionTime = 0.25f;
            FadeTime = 0.75f;
            SelectedColor = selectedColor;
            Font = Screen.ScreenManager.DefaultFont;
            menuEntries = new List<MenuEntry>(8);
        }

        #region Menu Entries

        public void Cancel()
        {
            Cancelled(this);
        }

        public void Select()
        {
            var entry = menuEntries[selectedIndex];
            entry.Selected(entry);
        }

        public void CheckItem(int index)
        {
            isTransitioning = true;

            var item = menuEntries[index];
            var color = item.Color;

            item.IsChecked = !item.IsChecked;

            if (item.IsChecked)
            {
                Screen.ScreenManager.Timers.CreateInterpolator(0, 1, TransitionTime, i =>
                {
                    item.Scale = MathHelper.Lerp(1f, selectedScale, i.Value);
                    item.Color = Color.Lerp(color, SelectedColor, i.Value);
                }, i => isTransitioning = false);
            }
            else
            {
                Screen.ScreenManager.Timers.CreateInterpolator(0, 1, TransitionTime, i =>
                {
                    item.Scale = MathHelper.Lerp(selectedScale, 1f, i.Value);
                    item.Color = Color.Lerp(color, Color.White, i.Value);
                }, i => isTransitioning = false);
            }
        }

        public void MoveToItem(int index, int newIndex)
        {
            isTransitioning = true;

            var newItem = menuEntries[newIndex];

            newItem.StopFading = false;

            if (index >= 0)
            {
                var oldItem = menuEntries[index];
                var oldColor = oldItem.Color;

                oldItem.StopFading = true;

                Screen.ScreenManager.Timers.CreateInterpolator(0, 1, TransitionTime, i =>
                {
                    oldItem.Color = Color.Lerp(oldColor, Color.White, i.Value);
                    oldItem.Scale = MathHelper.Lerp(selectedScale, 1f, i.Value);
                });
            }

            Screen.ScreenManager.Timers.CreateInterpolator(0, 1, TransitionTime, i =>
            {
                newItem.Scale = MathHelper.Lerp(1f, selectedScale, i.Value);
            }, i => isTransitioning = false);

            FadeIn(newItem);
        }

        #region Fading

        private void FadeIn(MenuEntry entry)
        {
            if (entry.StopFading)
                return;

            Screen.ScreenManager.Timers.InterpolateColor(Color.White, SelectedColor, FadeTime, color => 
            {
                if (entry.StopFading)
                {
                    FadeOut(entry);
                    return;
                }

                entry.Color = color;

            }, i => FadeOut(entry), InterpolatorScales.Quadratic);
        }

        private void FadeOut(MenuEntry entry)
        {
            if (entry.StopFading)
            {
                FadeOut(entry, TransitionTime, true);
            }
            else
            {
                FadeOut(entry, FadeTime, false);
            }
        }

        private void FadeOut(MenuEntry entry, float time, bool fadeOut)
        {
            if (fadeOut)
                Screen.ScreenManager.Timers.InterpolateColor(SelectedColor, Color.White, time,
                    color => entry.Color = color, null, InterpolatorScales.Quadratic);
            else
                Screen.ScreenManager.Timers.InterpolateColor(SelectedColor, Color.White, time,
                    color => entry.Color = color, i => FadeIn(entry), InterpolatorScales.Quadratic);
        }

        #endregion

        #endregion

        #region Transitioning

        public void TransitionOn()
        {
            Area = null;

            Screen.ScreenManager.Timers.CreateInterpolator
                (1, 0, TransitionTime, i => alpha = i.Value, null, InterpolatorScales.Quadratic);
        }

        public void TransitionOff()
        {
            originalArea = GetArea();

            Screen.ScreenManager.Timers.CreateInterpolator(0, 1, TransitionTime, i =>
            {
                alpha = i.Value;
                Area = new Rectangle(originalArea.X, originalArea.Y, 
                    (int)MathHelper.Lerp(originalArea.Width, originalArea.Width / 2, i.Value), originalArea.Height);
            }, i => Screen.Scenes.Remove(this));
        }

        #endregion

        #region Update and Draw

        private int GetWidth()
        {
            return Area.HasValue ? Area.Value.Width : Screen.ScreenManager.Width;
        }

        private Rectangle GetArea()
        {
            return Area.HasValue ? 
                Area.Value : new Rectangle(0, 0, Screen.ScreenManager.Width, Screen.ScreenManager.Height);
        }

        public void Update(GameTime gameTime, InputManager input)
        {
            if (!noInput)
            {
                if (!isTransitioning)
                {
                    if (input.Current.IsKeyMapDown(InputSettings.Up))
                    {
                        int oldIndex = selectedIndex;

                        selectedIndex--;

                        if (selectedIndex < 0)
                            selectedIndex = menuEntries.Count - 1;

                        MoveToItem(oldIndex, selectedIndex);
                    }
                    else if (input.Current.IsKeyMapDown(InputSettings.Down))
                    {
                        int oldIndex = selectedIndex;

                        selectedIndex++;

                        if (selectedIndex >= menuEntries.Count)
                            selectedIndex = 0;

                        MoveToItem(oldIndex, selectedIndex);
                    }
                }

                if (input.Current.IsKeyMapDown(InputSettings.Select) &&
                    input.Previous.IsKeyMapUp(InputSettings.Select))
                {
                    Select();
                }

                if (input.Current.IsKeyMapDown(InputSettings.Cancel))
                {
                    if (Cancelled != null)
                        Cancelled(this);
                }

                if (input.Current.IsKeyMapDown(InputSettings.Left) &&
                    input.Previous.IsKeyMapUp(InputSettings.Left))
                {
                    menuEntries[selectedIndex].Left();
                }
                else if (input.Current.IsKeyMapDown(InputSettings.Right) &&
                         input.Previous.IsKeyMapUp(InputSettings.Right))
                {
                    menuEntries[selectedIndex].Right();
                }
            }
            noInput = false;

            if (checkCount)
            {
                if (previousCount == 0 && menuEntries.Count > 0)
                {
                    MoveToItem(-1, 0);
                    checkCount = false;
                }

                previousCount = menuEntries.Count;
            }
        }

        public void UpdatePositions()
        {
            // Make the menu slide into place during transitions, using a
            // power curve to slow down as it nears the end.
            float transitionOffset = (float)Math.Pow(1 - alpha, 2);

            Vector2 position = new Vector2(0, Height);

            for (int i = 0; i < menuEntries.Count; i++)
            {
                var entry = menuEntries[i];

                // Center the entries.
                position.X = GetWidth() / 2 - entry.GetWidth(Font) / 2;

                entry.Position = position;

                position.Y += entry.GetHeight(Font);
            }
        }

        public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
        {
            UpdatePositions();

            for (int i = 0; i < menuEntries.Count; i++)
            {
                var entry = menuEntries[i];
                spriteBatch.DrawString(Font, entry.Text, entry.Position, 
                    Color.Lerp(entry.Color, Color.Transparent, alpha), 0f, 
                    Vector2.Zero, entry.Scale, SpriteEffects.None, 0);
            }
        }

        #endregion
        
        #endregion

        public void ReloadContent() { }

        #region Virtual IScene Members

        public virtual void Initialize() { }

        public virtual Rectangle? Area { get; set; }

        public virtual bool IsDebugging { get; set; }

        public virtual void Debug()
        {
            IsDebugging = true;
        }

        #endregion
    }
}
